(function ( exports ) {

    //region XrmValueBase

    function XrmValueBase () {
    }

    XrmValueBase.prototype = {
        namespace: '',
        namespaceHash: '',
        requireTypeDef: false,
        type: '',
        serialize: function ( sc ) {
            if ( typeof sc == 'undefined' ) {
                throw new Error(COSA.Util.StringFormat(COSA.Const.Error.SC_UNDEFINED), this.type);
            }

            var ltr = sc.letter(this.namespaceHash);

            if ( typeof ltr == 'undefined' ) {
                ltr = sc.register(this.namespace);
                sc.pushParent(ltr.ns(), this.namespace);
            }

            if ( this.requireTypeDef ) {
                sc.pushParent('i:type', ltr.tag(this.type));
            }

            return this.getTemplate(sc, ltr);
        },
        validate: function () { },
        getTemplate: function ( sc, ltr ) { },
        toKeyValuePair: function ( key ) {
            return new COSA.KeyValuePair(key, this);
        }
    };

    //endregion XrmValueBase

    //region XrmValue

    function XrmValue ( value ) {
        XrmValueBase.call(this);
        this.value = value;
    }

    COSA.Util.deriveOverride(XrmValueBase, XrmValue, {
        type: '',
        namespace: '',
        requireTypeDef: false,
        getTemplate: function () {
            return this.value;
        }
    });

    //endregion ValueBase

    //region Xrm2011ContractsValueBase

    function Xrm2011ContractsValueBase () {
        XrmValueBase.call(this)
    }

    COSA.Util.deriveOverride(XrmValueBase, Xrm2011ContractsValueBase, {
        namespace: 'http://schemas.microsoft.com/xrm/2011/Contracts',
        type: '',
        requireTypeDef: false
    });

    //endregion Xrm2011ContractsValueBase

    //region KeyValuePairBase

    function KeyValuePairBase ( key, value ) {
        XrmValueBase.call(this);
        this.value = value;
        this.key = key;
    }

    COSA.Util.deriveOverride(XrmValueBase, KeyValuePairBase, {
        namespace: 'http://schemas.datacontract.org/2004/07/System.Collections.Generic',
        type: 'KeyValuePairOfstringanyType',
        getTemplate: function ( sc, ltr ) {
            sc.snapshot();

            var keyNode = {
                    tag: ltr.tag('key'),
                    val: this.key
                },
                valueNode = {
                    tag: ltr.tag('value')
                },
                val_sr = this.value.serialize(sc);

            sc.appendToParent(valueNode);

            sc.restore();

            if ( Array.isArray(val_sr) ) {
                valueNode.nodes = val_sr;
            }
            else {
                valueNode.val = val_sr;
            }

            return [ keyNode, valueNode ];
        },
        validate: function () {
            (new ValueChecker(this.key))
                .defined()
                .typeOf('string');

            (new ValueChecker(this.value))
                .defined()
                .valid();
        }
    });

    //endregion KeyValuePairBase

    //region XrmEncoder

    function XrmEncoder () {
    }

    XrmEncoder.prototype = {
        encodeGuid: function ( guidString ) {
            var res = (guidString
                .toLowerCase()
                .match(/([0-9a-z]{8}\-[0-9a-z]{4}\-[0-9a-z]{4}-[0-9a-z]{4}\-[0-9a-z]{12})/));
            if ( res === null ) {
                throw new Error(COSA.Util.formatString(COSA.Const.Error.ENC_CANTENCODEGUID, guidString));
            }

            return res[0];
        },
        encodeDate: function ( date ) {
            var _pad = function ( num, len ) {
                var lngh = len || 2, res = num.toString();
                while ( res.length < lngh ) {
                    res = '0'.concat(res);
                }
                return res;
            };

            return (new COSA.Util.StrBuilder()).append(date.getFullYear().toString())
                .cappend('-', _pad(date.getMonth() + 1))
                .cappend('-', _pad(date.getDate()))
                .cappend('T', _pad(date.getHours()))
                .cappend(':', _pad(date.getMinutes()))
                .cappend(':', _pad(date.getSeconds()))
                .toString();
        },
        encodeString: function ( str ) {
            return encodeURIComponent(str)
                .replace(/['()]/g, function(c) {
                    return '%' + c.charCodeAt(0).toString(16);
                })
                .replace(/\*/g, '%2A');
        }
    };

    //endregion XrmEncoder

    //region ValueChecker

    function ValueChecker ( value ) {
        this.value = value;
    }

    ValueChecker.prototype = {
        defined: function () {
            this.notNull();
            if ( typeof this.value == 'undefined' ) {
                throw new Error();
            }
            return this;
        },
        notNull: function () {
            if ( this.value === null ) {
                throw new Error();
            }
            return this;
        },
        typeOf: function ( type ) {
            if ( typeof this.value != type ) {
                throw new Error();
            }
            return this;
        },
        instanceOf: function ( inst ) {
            if ( !(this.value instanceof inst) ) {
                throw new Error();
            }
            return this;
        },
        notEmptyOrWhitespace: function () {
            if ( /\\S/g.test(this.value) ) {
                throw new Error();
            }
            return this;
        },
        guid: function () {
            if ( !(/([0-9a-z]{8}\-[0-9a-z]{4}\-[0-9a-z]{4}-[0-9a-z]{4}\-[0-9a-z]{12})/.test(this.value)) ) {
                throw new Error(COSA.Util.formatError('VLD_NOTGUID', this.value));
            }
            return this;
        },
        int: function () {
            this.typeOf('number');
            if ( this.value % 1 !== 0 ) {
                throw new Error('Value is not integer');
            }
        },
        valid: function () {
            if ( typeof this.value.validate != 'undefined' ) {
                this.value.validate();
            }
        }
    };

    //endregion ValueChecker

    //region RootFault


    function RootFault () {
        this.faultcode = '';
        this.faultstring = '';
        this.detail = null;
    }

    RootFault.prototype.deserialize = function ( nodes ) {
        this.faultcode = nodes.faultcode.text();
        this.faultstring = nodes.faultstring.text();
        this.detail = (new Fault()).deserialize(nodes.detail.node('OrganizationServiceFault').namedNodes());

        return this;
    };

    //endregion RootFault

    //region Fault

    function Fault () {
        this.errorCode = 0;
        this.errorDetails = null;
        this.message = '';
        this.timestamp = '';
        this.innerFault = null;
        this.traceText = '';
    }

    Fault.prototype.deserialize = function ( nodes ) {
        this.errorCode = parseInt(nodes.ErrorCode.text());
        this.message = nodes.Message.text();
        this.timestamp = nodes.Timestamp.text();
        if ( typeof nodes.InnerFault.attr('i:nil') == 'undefined' ) {
            this.innerFault = (new Fault()).deserialize(nodes.InnerFault.namedNodes());
        }
        this.traceText = nodes.TraceText.text();

        return this;
    };

    //endregion Fault

    exports.XrmValueBase = XrmValueBase;
    exports.XrmValue = XrmValue;
    exports.Xrm2011ContractsValueBase = Xrm2011ContractsValueBase;
    exports.KeyValuePairBase = KeyValuePairBase;

    exports.XrmEncoder = XrmEncoder;
    exports.ValueChecker = ValueChecker;

    exports.RootFault = RootFault;
    exports.Fault = Fault;

})(COSA);
